/*
 * File         : cym_device.c
 * Version      : 0.1.0
 * Author       : Vincent Cheung
 * Date         : Oct. 10, 2013
 * Description  : All the operation of device.
 *
 * Copyright (C) 2013, CYM Co., Ltd.
 *
 * Change Logs  :
 * Date                 Author          Notes
 * Oct. 10, 2013        Vincent         first version
 *
 */

#include "cym_system.h"
#include <string.h>

/* device container list */
static list_t cym_device_container = CYM_LIST_OBJECT_INIT(cym_device_container);

/**
 * @addtogroup Device
 */

/*@{*/

/**
 * This function is used to register a new device to system.
 *
 * @param dev the device to be registered
 * @param name name of the device
 * @param flags the flag of the device
 *
 * @return return CYM_EOK when successfully, otherwise, return -CYM_ERROR
 */
serr_t cym_device_register(p_device_t dev, const char *name, uint16_t flags)
{
	/* the device can't be NULL */
	if (dev == CYM_NULL) 
	{
		return -CYM_ERROR;
	}

	/* the name must be unique */
	if (cym_device_find(name) != CYM_NULL) 
	{
		return -CYM_ERROR;
	}

	/* insert device to device container list */
	cym_list_insert_after(&cym_device_container, &(dev->parent.list));

	/* copy the name */
	strncpy(dev->parent.name, name, CYM_NAME_MAX);

	dev->parent.flag = flags;
	dev->open_flag   = CYM_DEVICE_OFLAG_CLOSE;

	return CYM_EOK;
}

/**
 * This function is used to initialize all the registered device.
 *
 * @return return CYM_EOK
 */
serr_t cym_device_init_all(void)
{
	struct k_device *device;
	struct k_object *object;
	list_t *node, *device_list;
	serr_t result;

	device_list = &cym_device_container;
	for (node = device_list->next; node != device_list; node = node->next)
	{
		/* get device through cym_device_container */
		object = CYM_LIST_ENTRY(node, struct k_object, list);
		device = (p_device_t)object;
		/* if the device has not initialized */
		if (device->init != CYM_NULL && 
		    !(device->parent.flag & CYM_DEVICE_FLAG_ACTIVATED))
		{
			result = device->init(device);
			if (result == CYM_EOK)
			{
				device->parent.flag |= CYM_DEVICE_FLAG_ACTIVATED;
			}
		}
	}

	return CYM_EOK;
}

/**
 * This function is used to initialize the device.
 *
 * @param dev the device to be initialized
 *
 * @return return the result
 */
serr_t cym_device_init(p_device_t dev)
{
	serr_t result = -CYM_ERROR;

	/* the device must have been activated */
	if (dev != CYM_NULL && dev->init != CYM_NULL)
	{
		if (dev->parent.flag & CYM_DEVICE_FLAG_ACTIVATED)
		{
			/* call the device init interface */
			result = dev->init(dev);
		}
	}

	return result;
}

/**
 * This function is used to find a device be the specified name.
 *
 * @param name the name of the device drivers
 *
 * @return return registered device driver if successfully, otherwise return CYM_NULL
 */
p_device_t cym_device_find(const char *name)
{
	struct k_device *device;
	list_t *node, *device_list;

	device_list = &cym_device_container;
	for (node = device_list->next; node != device_list; node = node->next)
	{
		/* get device through cym_device_container */
		device = CYM_LIST_ENTRY(node, struct k_device, parent.list);
		/* if the name is the same, return this device */
		if (strncmp(device->parent.name, name, CYM_NAME_MAX) == 0)
		{
			return device;
		}
	}

	/* not found the device */
	return CYM_NULL;
}

/**
 * This function is used to open a registered device.
 *
 * @param dev the device to be opened
 * @param oflag the flags for device on opening
 *
 * @return return the result
 */
serr_t cym_device_open(p_device_t dev, uint16_t oflag)
{
	serr_t result = -CYM_ERROR;

	if (dev != CYM_NULL)
	{
		/* if the device is not initialized, then initialize it */
		if (!(dev->parent.flag & CYM_DEVICE_FLAG_ACTIVATED))
		{
			if (dev->init != CYM_NULL)
			{
				result = dev->init(dev);
				/* if fail to initialize device, return */
				if (result != CYM_EOK)
				{
					return result;
				}
			}

			dev->parent.flag |= CYM_DEVICE_FLAG_ACTIVATED;
		}

		/* if the device has opened, return */
		if (dev->open_flag & CYM_DEVICE_OFLAG_OPEN)
		{
			return -CYM_EBUSY;
		}

		/* call the device open interface */
		if (dev->open != CYM_NULL)
		{
			result = dev->open(dev, oflag);
		}

		/* set the open flag */
		if (result == CYM_EOK)
		{
			dev->open_flag = oflag | CYM_DEVICE_OFLAG_OPEN;
		}
	}

	return result;
}

/**
 * This function is used to close an opened device.
 *
 * @param dev the device to be closed
 *
 * @return return the result
 */
serr_t cym_device_close(p_device_t dev)
{
	serr_t result = -CYM_ERROR;

	if (dev != CYM_NULL && dev->close != CYM_NULL)
	{
		/* call the device close interface */
		result = dev->close(dev);
	}

	/* set the open flag */
	if (result == CYM_EOK)
	{
		dev->open_flag |= CYM_DEVICE_OFLAG_CLOSE;
	}

	return result;
}

/**
 * This function is used to read data from device.
 *
 * @param dev poniter to device driver structure
 * @param pos the position of reading
 * @param buffer the data buffer to save read data
 * @param size the size of buffer
 *
 * @return the actually size that user has read from device on successfully, 
 *	   otherwise return 0
 */
usize_t cym_device_read(p_device_t dev, uoff_t pos,
                           void *buffer, usize_t size)
{
	usize_t read_size = 0;

	/* call the device read interface */
	if (dev != CYM_NULL && dev->read != CYM_NULL)
	{
		read_size = dev->read(dev, pos, buffer, size);
	}

	return read_size;
}

/**
 * This fuction is used to write data to device.
 *
 * @param dev pointer to device driver structure
 * @param pos the position of writing
 * @param buffer the data buffer to be written to device
 * @param size the size of buffer
 *
 * @return the actually size that user has written to device when successfully,
 * 	   otherwise, return 0
 */
usize_t cym_device_write(p_device_t dev, uoff_t pos,
                            const void *buffer, usize_t size)
{
	usize_t write_size = 0;

	/* call the device write interface */
	if (dev != CYM_NULL && dev->write != CYM_NULL)
	{
		write_size = dev->write(dev, pos, buffer, size);
	}

	return write_size;
}

/**
 * This function will provide a variety of control functions on device.
 *
 * @param dev the pointer of device driver structure
 * @param cmd the command sent to device
 * @param arg the argument of command
 *
 * @return return the reuslt
 */
serr_t cym_device_control(p_device_t dev, uint8_t cmd, void *arg)
{
	serr_t result = -CYM_ERROR;

	if (dev != CYM_NULL && dev->control != CYM_NULL)
	{
		/* call the device control interface */
		result = dev->control(dev, cmd, arg);
	}

	return result;
}

/**
 * This function will set the indicate callback function when the device 
 * receive data in ISR.
 *
 * @param dev the pointer of device driver structure
 * @param rx_ind the indication callback function
 *
 * @return return CYM_EOK
 */
serr_t cym_device_set_rx_indicate(p_device_t dev, serr_t (*rx_ind)(
                                     p_device_t dev, usize_t size))
{
	if (dev != CYM_NULL)
	{
		/* call the device rx indicate interface */
		dev->rx_indicate = rx_ind;
	}

	return CYM_EOK;
}

/**
 * This function will set the indicate callback function when the device has 
 * sent the data to physical hardware.
 *
 * @param dev the pointer of divice driver structure
 * @param tx_done the indication callback function
 *
 * @return CYM_EOK
 */
serr_t cym_device_set_tx_complete(p_device_t dev, serr_t (*tx_done)(
                                     p_device_t dev, void *buffer))
{
	if (dev != CYM_NULL)
	{
		/* call the device tx complete interface */
		dev->tx_complete = tx_done;
	}

	return CYM_EOK;
}

/*@}*/
